package au.edu.qut.bpm.genbinder.models;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ConcreteEventLog implements Cloneable {
	// TODO: All these methods go against OOP somewhat. So be it for now
	// Maybe change later to better format

	protected AbstractLog abstractLog;
	protected Binding binding;

	public Map<Activity, Long> boundStartTime;
	public Map<Activity, Set<Resource>> boundResources;

	// TODO: Indexing mechanism can be improved
	protected Map<Resource, Set<Activity>> reverseResourceIndex;
	//Map<Resource, Set<Activity>>reverseResourceIndex = new HashMap<Resource, Set<Activity>>();
	//============
	public Binding getBinding () {
		return binding;
	}
	
	public void setBinding (Binding binding) {
		this.binding = binding;
	}
	//==============
	/*
	public void setBoundStartTime (Map<Activity, Long> boundStartTime) {
		this.boundStartTime.clear();
		this.boundStartTime = boundStartTime;
	}
	
	public Map<Activity, Long> getBoundStartTime () {
		return this.boundStartTime;
	}
	 */
	public ConcreteEventLog(AbstractLog abstractLog) {
		this.abstractLog = abstractLog;
		/*
		for (Resource r : abstractLog.getResources())
			reverseResourceIndex.put(r, new HashSet<Activity>());
		
		Set<Activity> setAct = new HashSet<Activity>();
		
		for (Resource res : abstractLog.getResources()) {
			for (Activity a : abstractLog.getActivities()) {
				if (a.getResource().contains(res)) {
					setAct.add(a);
				}
			}
			reverseResourceIndex.put(res, setAct);
		}
		*/
		// Construct empty solution
		
		this.boundStartTime = new HashMap<Activity, Long>();
		this.boundResources = new HashMap<Activity, Set<Resource>>();
		for (Case c : abstractLog.getCases())
			for (Activity a : c.getActivities()) {
				boundStartTime.put(a, 0L);
				boundResources.put(a, new HashSet<Resource>());
			}
		
		// Set up the index
		this.reverseResourceIndex = new HashMap<Resource, Set<Activity>>();
		for (Resource r : abstractLog.getResources())
			reverseResourceIndex.put(r, new HashSet<Activity>());
			
	}

	public AbstractLog getAbstractLog() {
		return abstractLog;
	}
	
	public void setAbstractLog (AbstractLog abstractLog) {
		this.abstractLog = abstractLog;
	}
	
	protected Set<Activity> getIndexedActivitiesUsingResource(Resource r) {
		
		
		if (reverseResourceIndex.containsKey(r))
			return reverseResourceIndex.get(r);
		else
			return new HashSet<Activity>();
	}
	
	// Methods for activities
	
	public Set<Activity> getAllActivity() {
		Set<Activity> setAct = new HashSet<Activity>();
		
		for (Case c : abstractLog.getCases()) {
			for (Activity a : c.getActivities()) {
				setAct.add(a);
			}
		}
		
		return setAct;
	}

	public long getActivityStart(Activity activity) {
		
		//long a = this.boundStartTime.get(activity);
		/*
		if (this.boundStartTime.get(activity) == null) {
			System.out.println("problemhere-" + activity.getId() + " = " + this.boundStartTime.containsKey(activity));
		}
		*/
		
		return this.boundStartTime.get(activity);
		//return abstractLog.getActivity(activity).getStartTime();
	}

	public long getActivityEnd(Activity activity) {
		return getActivityStart(activity) + getActivityDuration(activity);
	}

	public long getActivityDuration(Activity activity) {
		return activity.getDuration();
	}
	
	public void addNewActivity(String id, String name, Case oCase, Task task, long startTime, long duration, Set<Resource> resource) {
		Activity a = new Activity(id, name, oCase, task, startTime, duration, resource);
		
		this.binding.addActivity(id, a);
	}

	public void setActivityStart(Activity activity, long time) throws NullPointerException {
		this.boundStartTime.put(activity, time);
		//try {
			//abstractLog.getActivity(activity).setStartTime(time);
		//} catch (Exception e) {
			
		//}
		
	}

	public Set<Resource> getActivityResources(Activity activity) {
		//return abstractLog.getActivity(activity).getResource();
		return this.boundResources.get(activity);
	}

	public void setActivityResources(Activity activity, Set<Resource> resources) throws NullPointerException {
		//try {
			//abstractLog.getActivity(activity).setResource(resources);
		//} catch (Exception e) {
			
		//}
		
		if (this.boundResources.containsKey(activity)) {
			for (Resource r : getActivityResources(activity))
				if (reverseResourceIndex.containsKey(r))
					reverseResourceIndex.get(r).remove(activity);
		}
		this.boundResources.put(activity, resources);
		for (Resource r : getActivityResources(activity)) {
			if (!reverseResourceIndex.containsKey(r))
				reverseResourceIndex.put(r, new HashSet<Activity>());
			reverseResourceIndex.get(r).add(activity);
		}
		
	}

	public boolean isActivityResourcesValid(Activity activity) {
		//return activity.getCase().getLog().isAllowedResourceSetFor(activity.getTask(), abstractLog.getActivity(activity).getResource());
		return activity.getCase().getLog().isAllowedResourceSetFor(activity.getTask(), this.boundResources.get(activity));
	}

	public boolean isActivityWithin(Activity activity, long start, long stop) {
		return getActivityStart(activity) < stop && getActivityEnd(activity) > start;
	}
	
	// Methods for cases

	public long getCaseArrival(Case caze) {
		return caze.getArrival();
	}

	public long getCaseStart(Case caze) {
		return getActivityStart(getSortedTrace(caze).get(0));
	}

	public long getCaseEnd(Case caze) {
		return getActivityEnd(getSortedTrace(caze).get(getSortedTrace(caze).size() - 1));
	}

	public long getCaseDuration(Case caze) {
		// Not using start here, was fixed in Wil's outline also
		return getCaseEnd(caze) - getCaseArrival(caze);
	}

	public List<Activity> getSortedTrace(Case caze) {
		List<Activity> sortedActivities = new ArrayList<Activity>(caze.getActivities());
		Collections.sort(sortedActivities, new Comparator<Activity>() {
			@Override
			public int compare(final Activity object1, final Activity object2) {
				return (int) (getActivityStart(object1) - getActivityStart(object2));
			}
		});
		return sortedActivities;
	}
	
	public List<Activity> getSortedActivities(LinkedList<Activity> act) {
		//List<Activity> sortedActivities = new ArrayList<Activity>(caze.getActivities());
		Collections.sort(act, new Comparator<Activity>() {
			@Override
			public int compare(final Activity object1, final Activity object2) {
				return (int) (getActivityStart(object1) - getActivityStart(object2));
			}
		});
		return act;
	}

	public boolean isCaseResourcesValid(Case caze) {
		for (Activity a : caze.getActivities())
			if (!isActivityResourcesValid(a))
				return false;
		return true;
	}

	public boolean isCaseNonOverlapping(Case caze) {
		if (caze.getActivities().size() <= 1)
			return true;
		List<Activity> sortedActivities = getSortedTrace(caze);
		long latestEnd = getActivityEnd(sortedActivities.get(0));
		for (int a = 1; a < sortedActivities.size(); a++) {
			if (getActivityStart(sortedActivities.get(a)) < latestEnd)
				return false;
			latestEnd = getActivityEnd(sortedActivities.get(a));
		}
		return true;
	}

	public boolean isCaseNonOverUtilizedResources(Case caze) {
		for (Activity a : caze.getActivities()) {
			for (Resource r : getActivityResources(a)) {
				for (Activity ao : getIndexedActivitiesUsingResource(r)) {
					if (ao.equals(a)) continue;
					if (isActivityWithin(a, getActivityStart(ao), getActivityEnd(ao)))
						return false;
				}
			}
		}
		return true;
	}

	public boolean isCaseOrdered(Case caze) {
		List<Activity> sortedActivities = getSortedTrace(caze);
		for (int start = 0; start < sortedActivities.size(); start++) {
			for (int next = start + 1; next < sortedActivities.size(); next++) {
				Activity first = sortedActivities.get(start);
				Activity second = sortedActivities.get(next);
				if (!caze.isOrderAllowed(first, second))
					return false;
			}
		}
		return true;
	}

	// Methods for log

	public Set<Resource> getResourcesUtilizedAt(long time) {
		return getResourcesUtilizedAt(time, null);
	}

	public Set<Resource> getResourcesUtilizedAt(long time, Case ignore) {
		return getResourcesUtilizedWithin(time, time, ignore);
	}

	public Set<Resource> getResourcesUtilizedWithin(long start, long end) {
		return getResourcesUtilizedWithin(start, end, null);
	}

	public Set<Resource> getResourcesUtilizedWithin(long start, long end,Case ignore) {
		Set<Resource> used = new HashSet<Resource>();
		for (Case c : abstractLog.getCases()) {
			if (c.equals(ignore))
				continue;
			for (Activity a : c.getActivities())
				if (isActivityWithin(a, start, end))
					used.addAll(getActivityResources(a));
		}
		return used;
	}
	
	public LinkedList<Activity> getClashingActivitiesWithin (Activity act, long start, long end, Set<Resource> setResource) {
		LinkedList<Activity> listClashActivities = new LinkedList<Activity>();
		//Set<Resource> setResource = new HashSet<Resource>();
		//setResource.add(res);
		
		for (Case c : abstractLog.getCases()){
			for (Activity a : c.getActivities()) {
				if (isActivityWithin(a, start, end) && (!a.equals(act))) {
					if (getActivityResources(a).equals(setResource)) {
						listClashActivities.add(a);
					}						
				}					
			}				
		}			

		return listClashActivities;
	}
	
	//public boolean checkWhetherResourceIsUtilizedWithin(long start, long end, String res) {
	//	return checkWhetherResourceIsUtilizedWithin(start, end, null, res);
	//}
	
	public boolean checkWhetherResourceIsUtilizedWithin(long start, long end, List<Activity> sortedActivity, Resource res) {
		
		if (sortedActivity.size() == 0) {
			return false;
		}
		
		boolean resourceAlreadyUtilized = false;
		
		Set<Resource> alreadyUsed = new HashSet<Resource>();
		
		for (Activity a : sortedActivity)
			if (isActivityWithin(a, start, end))
				alreadyUsed.addAll(getActivityResources(a));
		
		if (alreadyUsed.contains(res))
			resourceAlreadyUtilized = true;
		
		return resourceAlreadyUtilized;
	}

	public double getResourceUtilization(Resource r, long horizon, long horizonstart) {
		long startTime = horizonstart;
		long stopTime = horizonstart + horizon;
		long sum = 0;
		
		if (startTime < 0)
			startTime = 0;
		
		for (Activity a : getAbstractLog().getActivities()) {
			//System.out.println(a.getDuration());
			if (isActivityWithin(a, startTime, stopTime) && getActivityResources(a).contains(r)) {
				long aStart = getActivityStart(a) < startTime
						? startTime
						: getActivityStart(a);
				long aStop = getActivityEnd(a) >= stopTime
						? stopTime
						: getActivityEnd(a);
				sum += aStop - aStart;
				//System.out.println("start-" + startTime + " stop-" + stopTime + " time-" + time + " horizon-" + horizon + " astart-" + aStart + " astop-" + aStop + " sum-" + sum);
			}
		}
		return (double) sum;
		//return (double) sum / (double) (stopTime - startTime);
	}

	public long getLogArrival() {
		long f = -1;
		for (Case caze : abstractLog.getCases())
			if (getCaseArrival(caze) < f || f < 0)
				f = getCaseArrival(caze);
		return f;
	}

	public long getLogStart() {
		long f = -1;
		/*
		Map<Activity, Long> boundStartTime = new HashMap<Activity, Long>();
		
		for (Activity a : abstractLog.getActivities()) {
			boundStartTime.put(a, a.getStartTime());
		}
		*/
		for (Activity a : boundStartTime.keySet())
			if (getActivityStart(a) < f || f < 0)
				f = getActivityStart(a);
		return f;
	}

	public long getLogEnd() {
		long f = -1;
		/*
		Map<Activity, Long> boundStartTime = new HashMap<Activity, Long>();
		
		for (Activity a : abstractLog.getActivities()) {
			boundStartTime.put(a, a.getStartTime());
		}
		*/
		for (Activity a : boundStartTime.keySet())
			if (getActivityEnd(a) > f || f < 0)
				f = getActivityEnd(a);
		return f;
	}

	public boolean isLogSafe() {
		for (Case c : abstractLog.getCases()) {
			if (!isCaseOrdered(c))
				return false;
			//if (!isCaseNonOverlapping(c))
				//return false;
			if (!isCaseNonOverUtilizedResources(c))
				return false;
		}
		return true;
	}

	public boolean isLogValid() {
		for (Case c : abstractLog.getCases())
			if (!isCaseResourcesValid(c))
				return false;
		return isLogSafe();
	}

	public List<Set<Activity>> getLogViolatingActivities() {
		List<Set<Activity>> violatingActivities = new ArrayList<Set<Activity>>();
		for (int i = 0; i < 4; i++)
			violatingActivities.add(new HashSet<Activity>());
		// 0: Overlap
		// 1: Misorder
		// 2: Resourceclash
		// 3: Invalidresource

		for (Case c : abstractLog.getCases()) {

			List<Activity> sortedActivities = getSortedTrace(c);
			long latestEnd = getActivityEnd(sortedActivities.get(0));
			for (int ac = 0; ac < sortedActivities.size(); ac++) {

				Activity a = sortedActivities.get(ac);
				if (ac > 0 && getActivityStart(a) < latestEnd)
					violatingActivities.get(0).add(a);
				latestEnd = getActivityEnd(a);
				for (int next = ac + 1; next < sortedActivities.size(); next++) {

					Activity second = sortedActivities.get(next);
					if (!c.isOrderAllowed(a, second))
						violatingActivities.get(1).add(a);
				}
				for (Resource r : getActivityResources(a)) {

					for (Activity ao : getIndexedActivitiesUsingResource(r)) {

						if (ao.equals(a)) continue;
						if (isActivityWithin(a, getActivityStart(ao), getActivityEnd(ao)))
							violatingActivities.get(2).add(a);
					}
				}
				if (!isActivityResourcesValid(a))
					violatingActivities.get(3).add(a);
			}
		}

		return violatingActivities;
	}
	
	public LinkedList<Activity> getSortedActivity(LinkedList<Activity> sortedActivities) {
		//List<Activity> sortedActivities = new ArrayList<Activity>(caze.getActivities());
		Collections.sort(sortedActivities, new Comparator<Activity>() {
			@Override
			public int compare(final Activity object1, final Activity object2) {
				return (int) (getActivityStart(object1) - getActivityStart(object2));
			}
		});
		return sortedActivities;
	}
	
	public LinkedList<Activity> getFixedTraceOrderAndOverlap(LinkedList<Activity> activities) {
		activities = getSortedActivity(activities);
		long endTime = 0;
		
		for (Activity a : activities) {
			long startTime = a.getStartTime();
			if (startTime < endTime) {
				a.setStartTime(endTime);
			}
			endTime = startTime;
		}
		
		return activities;
	}

	public ConcreteEventLog clone() {
		ConcreteEventLog newLog = new ConcreteEventLog(abstractLog);
		for (Activity a : abstractLog.getActivities())
			newLog.setActivityStart(a, getActivityStart(a));
		for (Activity a : abstractLog.getActivities())
			newLog.setActivityResources(a, getActivityResources(a));
		/*
		for (Activity a : boundStartTime.keySet())
			newLog.setActivityStart(a, getActivityStart(a));
		for (Activity a : boundResources.keySet())
			newLog.setActivityResources(a, getActivityResources(a));
		*/
		return newLog;
	}
	
}
